home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / Libraries / Sherlock 2.0 / Mac v2.0 docs / Text Docs / Mac Debugging .txt next >
Text File  |  1996-04-05  |  10KB  |  246 lines

  1. Debugging With Sherlock
  2.  
  3.  
  4. This chapter offers tips on how to find and correct bugs more effectively.  C programs are 
  5. particularly challenging to debug because the C language does not limit what you can do.  This 
  6. Chapter is based on parts of the book Debugging C, with the permission of Robert Ward.  
  7.  
  8.  
  9. Pointer Bugs
  10.  
  11. The key to learning how to debug C programs is recognizing, understanding and correcting 
  12. pointer bugs.  Pointers pervade all of C and they give the C language much of its power and 
  13. flexibility.  However, pointers are dangerous, as well as useful.
  14.  
  15.  
  16. This section addresses the most common situations involving pointer bugs:
  17.  
  18.  
  19. • Pointer bugs arise from uninitialized pointers, dangling pointers and incorrect use of arguments 
  20. to function.
  21.  
  22. • Pointer bugs often destroy the computer code.  The code destroyed by pointer bugs may have 
  23. been the code you wrote, code comprising run-time functions such as printf(), or operating system 
  24. code.
  25.  
  26. • To the new C programmer, pointer bugs produce symptoms that look like hardware malfunctions 
  27. or compiler bugs.  The experienced C programmer recognizes these same symptoms as clear 
  28. indications of the existence of pointer bugs.
  29.  
  30.  
  31. Types of Pointer Bugs
  32.  
  33. Pointer bugs arise from uninitialized pointers, dangling pointers and incorrect use of arguments to 
  34. functions.  An uninitialized pointer is simply a pointer variable which is used before it has been 
  35. given a value.  For example,
  36.  
  37.  
  38.     error1()
  39.  
  40.     {
  41.  
  42.         char *p;
  43.  
  44.         *p = 'a';
  45.  
  46.     }
  47.  
  48.  
  49. In this example, p does not point to any specified location at the time that it used to store the 
  50. character 'a'.  The location in memory is not specified by the code, and the results are 
  51. unpredictable.  Possible symptoms will be discussed later.
  52.  
  53. The second type of pointer bug is the dangling pointer.  A dangling pointer refers to an area of 
  54. memory which is no longer being used for its original purpose.  For example,
  55.  
  56.  
  57.     error2()
  58.  
  59.     {
  60.  
  61.         char *p, *malloc();
  62.  
  63.         p = malloc(25);
  64.  
  65.         free(p);
  66.  
  67.     }
  68.  
  69.     
  70.  
  71. The call to malloc() makes p point to an area of memory containing room for 25 characters.  The 
  72. call to free() deallocates the space and causes p to become a dangling pointer. However, there is no 
  73. bug in this code.  Any time you deallocate memory you create a dangling pointer—bugs arise from 
  74. using dangling pointers rather than just creating them.  
  75.  
  76.  
  77. Again, the results of using a dangling pointer are unpredictable.  They are not specified by the C 
  78. program.  In this example, if the memory released by the call to free() is later reallocated, using p 
  79. will probably corrupt the newly allocated memory.
  80.  
  81.  
  82. A third kind of pointer bug is the mistaken parameter bug.  A good example is the following:
  83.  
  84.  
  85.     error3()
  86.  
  87.     {
  88.  
  89.         int i;
  90.  
  91.         scanf("%d", i);
  92.  
  93.     }
  94.  
  95.  
  96. This will not work as expected.  The second argument to scanf() should have been &i, not i.  The 
  97. scanf() function expects a pointer to i and gets the value of i instead.  Thus, the pointer that scanf() 
  98. expects is incorrect.  Once again this creates a hard-to-find bug.
  99.  
  100.  
  101. This kind of error can corrupt the system stack.  This happens because the called routines assume 
  102. the stack has a different structure from the stack that was actually created by the caller.  If the called 
  103. program alters any stack variable, it will be altering a part of the stack that may not actually 
  104. correspond to the location of the stack variable.
  105.  
  106.  
  107. This kind of bug can be eliminated by using function prototyping.
  108.  
  109. Effects of Pointer Bugs
  110.  
  111. Pointer bugs can be devastating.  Any pointer bug has the potential for destroying any part of 
  112. memory—executable code, library functions such as printf(), the system stack used to keep track 
  113. of procedure calls and returns or even the operating system itself.  Exactly which part of memory 
  114. depends on the value the pointer had at the time your program was loaded.
  115.  
  116.  
  117.  
  118. Symptoms of Pointer Bugs
  119.  
  120. The symptoms of pointer bugs vary depending on just what kind of code or data area are 
  121. destroyed by the bug.  Such symptoms can not be taken at face value. When confronted with 
  122. behavior such as described below, always think first of pointer bugs.
  123.  
  124.  
  125. Symptom : Your program crashes inside a function which you have written and which you know 
  126. to be debugged.
  127.  
  128. Cause:    A pointer bug has destroyed your carefully debugged function.
  129.  
  130.  
  131. Symptom: A library function suddenly ceases to work correctly.
  132.  
  133. Cause:    A pointer bug has destroyed the library function instead of your own code.
  134.  
  135.  
  136. Symptom : A bug is solid, i.e., it manifests itself in the same way when you run the program 
  137. several times.  However, the bug goes away when you insert print statements into the code to get 
  138. more information about it.
  139.  
  140. Cause:    Inserting the print statements changed the location of various parts of your code.  Before 
  141. the print statements were inserted, the pointer bug destroyed code that was still to be executed.  
  142. After inserting the print statements, the pointer bug destroyed a part of the program which was no 
  143. longer executed after the pointer bug occurred.
  144.  
  145.  
  146. Symptom: The symptoms of a bug change after you insert print statements.
  147.  
  148. Cause:    Inserting print statements changed the code destroyed by the pointer bug.
  149.  
  150.  
  151. Symptom: By inserting print statements you can determine that control reaches a particular 
  152. statement but not the statement immediately following.
  153.  
  154. Cause:    A pointer bug has destroyed one or both of the statements.
  155.  
  156.  
  157. Symptom: Your program calls a function, but control never reaches the function.
  158.  
  159. Cause:    A pointer bug has destroyed either the system stack or the function itself.
  160.  
  161. Symptom: Control never returns to the caller of a function after the called function returns.
  162.  
  163. Cause:    A pointer bug has destroyed either the run-time stack or the function itself.
  164.  
  165.  
  166. Symptom: Your program sometimes works and sometimes crashes.
  167.  
  168. Cause:    An uninitialized variable is destroying random parts of your program depending on the 
  169. contents of certain memory locations before your program was invoked.  The pointer bug is 
  170. destroying different memory locations each time your program is being run.  Sometimes the 
  171. destroyed locations do not affect your program and sometimes they do.
  172.  
  173.  
  174. Symptom: Your program always works once, but fails the second time it is run.
  175.  
  176. Cause:    Your program contains an uninitialized variable.  The first time your program runs the 
  177. variable is initialized in a uniform way which does not cause any apparent harm.  The second time 
  178. your program runs, the variable has a new initial value which depends on the program’s first run.  
  179. This second initial value causes the symptoms the second time the program is run.
  180.  
  181.  
  182.  
  183. Using Sherlock to Find Bugs
  184.  
  185. Using Sherlock to locate bugs is a two-step process: 1) make the bug happen consistently and
  186.  
  187. 2) make a plausible guess about its cause.
  188.  
  189.  
  190. The first step in finding the cause of any bug is to make the bug “stand still” so that you can study 
  191. it.  You can do this in the following ways:
  192.  
  193.  
  194. 1. Fill memory with a constant value before you run your program.  This will insure that 
  195. uninitialized pointers get the same (though probably still incorrect) value from run to run.  If filling 
  196. memory with a constant value makes the symptoms of your bug go away, you can be reasonably 
  197. sure that some kind of initialization problem is causing the bug.
  198.  
  199.  
  200. 2. Eliminate the effects (including symptoms) of most dangling pointers by disabling any routine 
  201. which frees a dynamically allocated data structure.  You can do that by providing an alternative 
  202. deallocation routine which is a dummy routine. When you do that, dangling pointers no longer 
  203. dangle, i.e., they no longer point to deallocated memory.  If disabling a routine such as free() 
  204. makes the symptoms of your bug go away, you can be reasonably sure that a dangling pointer is at 
  205. hand.
  206.  
  207.  
  208.  
  209. 3. Keep precise records about how you invoked your program.  An easy way to do this is by 
  210. invoking the program from a batch file, also known as a submit file or shell file. That way the 
  211. batch file serves as a record for exactly what you have done.  A tip:  when you change the 
  212. arguments to your program, comment out the old line and save it in the batch file as a record of the 
  213. your previous runs.  This is especially handy when using numerous Sherlock tracepoints to 
  214. change the behavior of your program during testing.
  215.  
  216.  
  217. 4. Keep your program unchanged.  Since pointer bugs destroy code, even the smallest change in 
  218. your program, or even a change in the order in which functions are linked together, may cause the 
  219. symptoms of pointer bugs to change or even go away.  Sherlock is a huge help here.  Enabling or 
  220. disabling different tracepoints does not change the location of any code in your program.  You can 
  221. run test after test on your program, getting very different information each time, and the symptoms 
  222. of pointer bugs will not change.
  223.  
  224.  
  225. The second step in finding a bug is to make a guess about what is causing it.  If any of the 
  226. symptoms mentioned above appear, you probably should assume that a pointer bug is causing 
  227. your problems.  Use Sherlock to look for bad pointers by tracing the parameters passed to all your 
  228. functions.  
  229.  
  230.  
  231. If a supposed pointer has a small negative number as its value (.e.g., FFFFE hex), you can be 
  232. sure that the pointer has already been corrupted.  You may also notice that a pointer does not have 
  233. a reasonable value in some other way.  Now ask yourself, “Which routines could have passed that 
  234. pointer on to the called routine?”  Rerun your program with different tracepoints enabled which 
  235. will trace the likely culprits.  Once you find the source of a bad pointer, ask whether the code 
  236. which created the bad pointer was ultimately at fault or whether some other error resulted in the 
  237. bad pointer as a by-product.
  238.  
  239.  
  240. You will find that patterns jump out at you as you look at traces and dumps produced by Sherlock.  
  241. When you get a hint of something in a trace being not quite right, you can immediately start a new 
  242. trace to zero in on those parts of the program that are related to what caught your attention.  By 
  243. varying your traces to home in on suspects, you are eliminating vast amounts of extraneous 
  244. information in the debugging output.
  245.  
  246.